package com.yaorange.nongjt.certificate.service.impl;

import java.io.*;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

import com.github.pagehelper.PageHelper;
import com.yaorange.nongjt.certificate.bo.CertificatePrintBo;
import com.yaorange.nongjt.certificate.bo.NjtCertificateBo;
import com.yaorange.nongjt.certificate.domain.NjtCertificateCount;
import com.yaorange.nongjt.certificate.domain.NjtUserPerfect;
import com.yaorange.nongjt.certificate.mapper.NjtCertificateCountMapper;
import com.yaorange.nongjt.certificate.mapper.NjtUserPerfectMapper;
import com.yaorange.nongjt.common.core.domain.AjaxResult;
import com.yaorange.nongjt.common.core.domain.model.LoginUser;
import com.yaorange.nongjt.common.enums.YesOrNoEnum;
import com.yaorange.nongjt.common.exception.ServiceException;
import com.yaorange.nongjt.common.utils.DateUtils;
import com.yaorange.nongjt.common.utils.PageUtils;
import com.yaorange.nongjt.common.utils.SecurityUtils;
import com.yaorange.nongjt.common.utils.bean.BeanUtils;
import com.yaorange.nongjt.common.utils.file.FileUploadUtils;
import com.yaorange.nongjt.common.utils.nongjt.QrCodeCreateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.io.FileUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import com.yaorange.nongjt.certificate.mapper.NjtCertificateMapper;
import com.yaorange.nongjt.certificate.domain.NjtCertificate;
import com.yaorange.nongjt.certificate.service.INjtCertificateService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

/**
 * 合格证打印数据Service业务层处理
 *
 * @author coach tam
 * @date 2024-10-18
 */
@Slf4j
@Service
public class NjtCertificateServiceImpl implements INjtCertificateService
{
    @Autowired
    private NjtCertificateMapper njtCertificateMapper;
    @Autowired
    private NjtCertificateCountMapper njtCertificateCountMapper;
    @Autowired
    private NjtUserPerfectMapper userPerfectMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Value("${nongjt.uploadPath}")
    private String uploadPath;
    @Autowired
    private CommonService commonService;
    //新开一个单线程池
    private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();

    //定义一个线程池,核心线程数 cpu*2=8  , 最大线程数 50, 队列最大长度 1000,拒绝策略 让调用线程执行,线程工厂,存活时间（60），存活时间单位（秒）
    private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(8, 50, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
    /**
     * 查询合格证打印数据
     *
     * @param id 合格证打印数据主键
     * @return 合格证打印数据
     */
    @Override
    public NjtCertificate selectNjtCertificateById(Long id)
    {
        return njtCertificateMapper.selectNjtCertificateById(id);
    }

    /**
     * 查询合格证打印数据列表
     *
     * @param njtCertificate 合格证打印数据
     * @return 合格证打印数据
     */
    @Override
    @Transactional(readOnly = true)
    public List<NjtCertificateBo> selectNjtCertificateList(NjtCertificateBo njtCertificate)
    {
        commonService.getAuthNjtCertificate(njtCertificate);
        PageUtils.startPage();
        return njtCertificateMapper.selectNjtCertificateList(njtCertificate);
    }

    /**
     * 新增合格证打印数据
     *
     * @param njtCertificate 合格证打印数据
     * @return 结果
     */
    @Override
    public int insertNjtCertificate(NjtCertificate njtCertificate)
    {
        njtCertificate.setCreateTime(DateUtils.getNowDate());
        return njtCertificateMapper.insertNjtCertificate(njtCertificate);
    }

    /**
     * 修改合格证打印数据
     *
     * @param njtCertificate 合格证打印数据
     * @return 结果
     */
    @Override
    public int updateNjtCertificate(NjtCertificate njtCertificate)
    {
        njtCertificate.setUpdateTime(DateUtils.getNowDate());
        return njtCertificateMapper.updateNjtCertificate(njtCertificate);
    }

    /**
     * 批量删除合格证打印数据
     *
     * @param ids 需要删除的合格证打印数据主键
     * @return 结果
     */
    @Override
    public int deleteNjtCertificateByIds(Long[] ids)
    {
        return njtCertificateMapper.deleteNjtCertificateByIds(ids);
    }

    /**
     * 删除合格证打印数据信息
     *
     * @param id 合格证打印数据主键
     * @return 结果
     */
    @Override
    public int deleteNjtCertificateById(Long id)
    {
        return njtCertificateMapper.deleteNjtCertificateById(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<String> batchCode(final CertificatePrintBo certificatePrintBo) {
        final List<String> codeList = new ArrayList<>();
        //1.合格证统计表(1)
        NjtCertificateCount njtCertificateCount = new NjtCertificateCount();
        //两个实体之间如果多个字段名字相同，那么可以通过反射工具类赋值
        BeanUtils.copyProperties(certificatePrintBo,njtCertificateCount);

        LoginUser loginUser = SecurityUtils.getLoginUser();
        final NjtUserPerfect njtUserPerfect = userPerfectMapper.selectNjtUserPerfectById(loginUser.getUserId());
        BeanUtils.copyProperties(njtUserPerfect,njtCertificateCount);

        final Date printDate = new Date();
        /*njtCertificateCount.setPrintDate(printDate);*/
        njtCertificateCount.setCreateTime(printDate);
        njtCertificateCount.setCreateBy(SecurityUtils.getUsername());
        njtCertificateCountMapper.insertNjtCertificateCount(njtCertificateCount);

        //2.合格证详情表(N)
        //比如打印10份，那么就应该遍历10，生成10条临时合格证
        List<Future<NjtCertificate>> futureList = new ArrayList<>();
        Long printNumber = certificatePrintBo.getPrintNumber();
        final List<NjtCertificate> njtCertificateList = new ArrayList<>(printNumber.intValue());
        String username = SecurityUtils.getUsername();
        Long userId = SecurityUtils.getUserId();
        for (int i = 0; i < printNumber; i++) {
            //将任务提交给线程池
            Future<NjtCertificate> future = EXECUTOR.submit(() -> {
                NjtCertificate njtCertificate = new NjtCertificate();
                BeanUtils.copyProperties(certificatePrintBo, njtCertificate);
                njtCertificate.setCreateBy(username);
                njtCertificate.setUserId(userId);
                njtCertificate.setCreateTime(printDate);
                log.debug("生成合格证编号-开始");
                String code = this.getCode(njtUserPerfect, printDate);
                log.debug("生成合格证编号-结束");
                codeList.add(code);
                njtCertificate.setCode(code);
                log.debug("生成二维码图片-开始");
                String qrCode = this.getQrCode(code);
                log.debug("生成二维码图片-结束");
                njtCertificate.setQrcode(qrCode);
                njtCertificate.setDelFlag(YesOrNoEnum.YES.CODE);
                return njtCertificate;
//                njtCertificateList.add(njtCertificate);
            });
            futureList.add(future);
//            njtCertificateMapper.insertNjtCertificate(njtCertificate);
        }
        //等待所有任务执行完毕后
        for (Future<NjtCertificate> future : futureList) {
            try {
                NjtCertificate njtCertificate = future.get();
                njtCertificateList.add(njtCertificate);
            } catch (Exception e) {
                log.error("获取线程执行结果异常", e);
                throw new ServiceException("获取线程执行结果异常");
            }
        }
        //将多条insert sql 合并成了一条insert sql,将响应时间从4s提升了3s
        njtCertificateMapper.batchInsertNjtCertificate(njtCertificateList);
        return codeList;
    }

    /**
     * 生成一个二维码，并且上传到七牛云
     * @return 二维码地址
     */
    private String getQrCode(String code) {
        File file = null;
        try {
            //通过zxing创建二维码图片
            file = QrCodeCreateUtil.doCreateQrCode(uploadPath + "//code", code, "http://localhost:8080/code/" + code, 300, true);
            //将本地的二维码图片上传到七牛云
            //将File转成FileItem
            FileItem fileItem = FileUploadUtils.createFileItem(file, "file");
            //将FileItem转成MultipartFile
            CommonsMultipartFile commonsMultipartFile = new CommonsMultipartFile(fileItem);
            return commonService.upload(commonsMultipartFile);
        } finally {
            //删除临时文件
            file.delete();
        }
    }

    /**
     * 会员完善信息表地址编码前6位（510117）+ 年月日（20230002）+ 6位递增数（000001，通过Redis实现）
     */
    private String getCode(NjtUserPerfect njtUserPerfect, Date printDate) {
        String addressCode = njtUserPerfect.getAddressCode().substring(0,5);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
        String yyyyMMdd = simpleDateFormat.format(printDate);
        Long increment = redisTemplate.opsForValue().increment("certificate:" + yyyyMMdd);
        // 不足6位，在前面补0
//        String code = addressCode + yyyyMMdd + StringUtils.leftPad(increment.toString(),6,"0");
        //使用String.format实现补0
        return String.format("%s%s%06d",addressCode,yyyyMMdd,increment);
    }

    /**
     * 批量保存打印成功记录
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult batchSaveByCode(List<String> codeList) {
        for (String code : codeList) {
            NjtCertificate certificate = new NjtCertificate();
            certificate.setCode(code);
            // 删除标记设置为非删除
            certificate.setDelFlag(YesOrNoEnum.NO.CODE);
            // 打印次数默认为1次
            certificate.setPrintNumber(1L);
            // 修改创建时间为实际打印时间
            certificate.setCreateTime(new Date());
            // 修改更新时间为实际打印时间
            certificate.setUpdateTime(new Date());
            Integer updatePrintNumber = njtCertificateMapper.updateByCode(certificate);
            if (updatePrintNumber <= YesOrNoEnum.NO.CODE) {
                return AjaxResult.error("保存打印数据失败");
            }
        }

        return AjaxResult.success("保存打印数据成功");
    }

    private AtomicInteger progress = new AtomicInteger(0);

    //初始化一个独占锁
//    private ReentrantLock lock = new ReentrantLock(true);
    private Semaphore lock = new Semaphore(1);
    @Override
    public  String  exportAll(NjtCertificateBo njtCertificate, HttpServletResponse response)  {
        String fileName = UUID.randomUUID() +"_" +DateUtils.dateTime()+".xlsx";
        if(!lock.tryAcquire()){
            throw new ServiceException("当前有其他线程正在导出，请稍后再试");
        }
        //新开线程异步执行创建临时文件 (单线程池，固定线程池，调度，缓存)
        EXECUTOR_SERVICE.execute(()->{
            Workbook sheets = null;
            OutputStream outputStream = null;
            //1.创建excel
            try {
                //1.1 读取模板
                ClassPathResource resource = new ClassPathResource("excel/certificate_print_template.xlsx");
                String path = resource.getURL().getFile();
                FileInputStream fis = new FileInputStream(path);
                sheets = WorkbookFactory.create(fis);
                Sheet sheet = sheets.getSheet("合格证打印数据数据");
                //1.2 分批次查询数据库数据
                int size = 0;
                int pageSize = 1000;
                int pageNum = 1;
                //查询到总记录数
                int totalCount = njtCertificateMapper.selectTotalCount();
                int sumCount = 0;
                do {
                    //读一页数据，然后写到excel
                    PageHelper.startPage(pageNum++, pageSize);
                    List<NjtCertificateBo> list = njtCertificateMapper.selectNjtCertificateList(njtCertificate);
                    size = list.size();
                    sumCount+=size;
                    //将数据写入到excel中
                    this.writeExcel(sheet, list);
                    //将sheets保存到硬盘
                    String filePath = uploadPath+File.separator+fileName;
                    outputStream = Files.newOutputStream(Paths.get(filePath));
                    sheets.write(outputStream);
                    progress.set((sumCount*100 / totalCount));
                    log.debug("创建临时文件进度：" + progress);
                } while (size == pageSize);
            } catch (IOException e) {
                throw new ServiceException("创建excel异常");
            }finally {
                try {
                    assert sheets != null;
                    sheets.close();
                } catch (IOException e) {
                    log.error("关闭工作表异常",e);
                }
                try {
                    assert outputStream != null;
                    outputStream.close();
                } catch (IOException e) {
                    log.error("关闭输出流异常",e);
                }
                //释放锁
                lock.release();
            }
        });
        return fileName;
    }

    @Override
    public Integer queryProgress() {
        return progress.get();
    }

    @Override
    public void download(String tempFile, HttpServletResponse response) {
        //2.下载excel文件
        String filePath = uploadPath + File.separator + tempFile;
        try (FileInputStream fis = new FileInputStream(filePath)){
            //设置响应头
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            //设置文件名
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(tempFile, "UTF-8"));

            //准备输出流
            ServletOutputStream os = response.getOutputStream();
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            //刷新输出流
            os.flush();
        } catch (Exception e) {
            log.error("导出excel异常",e);
            throw new ServiceException("导出excel异常");
        }finally {
            //删除临时文件
            boolean delete = new File(filePath).delete();
            if (!delete) {
                log.error("删除临时文件失败");
            }
        }
    }


    private void writeExcel(Sheet sheet, List<NjtCertificateBo> list) {
        for (NjtCertificateBo cer : list){
            int lastRowNum = sheet.getLastRowNum();
            //创建行
            Row row = sheet.createRow(++lastRowNum);
            //创建列
            Cell cell = row.createCell(0);
            cell.setCellValue(cer.getUserId());

            cell = row.createCell(1);
            cell.setCellValue(cer.getProductName());
        }
    }
}
